package com.heima.article.job;
import com.alibaba.fastjson.JSON;
import com.heima.article.service.HotArticleService;
import com.heima.common.constants.article.HotArticleConstants;
import com.heima.model.mess.app.AggBehaviorDTO;
import com.heima.model.mess.app.NewBehaviorDTO;
import com.xxl.job.core.biz.model.ReturnT;
import com.xxl.job.core.handler.annotation.XxlJob;
import lombok.extern.slf4j.Slf4j;
import org.checkerframework.checker.units.qual.A;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.connection.RedisConnection;
import org.springframework.data.redis.core.ListOperations;
import org.springframework.data.redis.core.RedisCallback;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.serializer.RedisSerializer;
import org.springframework.stereotype.Component;
import org.springframework.util.CollectionUtils;

import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.stream.Collectors;

@Component
@Slf4j
public class UpdateHotArticleJob {

    @Autowired
    private StringRedisTemplate redisTemplate;

    @Autowired
    private HotArticleService hotArticleService;





    @XxlJob("updateHotArticleJob")
    public ReturnT updateHotArticleHandler(String params){
        log.info("热文章分值更新 调度任务开始执行....");

        //获取redis中的待处理的数据
        List<NewBehaviorDTO> behaviorList = getRedisBehaviorList();
        if (CollectionUtils.isEmpty(behaviorList)){
            log.info("热文章分值更新: 太冷清了 未产生任何文章行为 调度任务完成....");
            return ReturnT.SUCCESS;
        }

        //将数据按照文章分组 , 进行聚合同级 得到待更新的数据列表
        List<AggBehaviorDTO> aggBehaviorList = getAggBehaviorList(behaviorList);
        if (CollectionUtils.isEmpty(aggBehaviorList)){
            log.info("热文章分值更新: 太冷清了 未产生任何文章行为 调度任务完成....");
            return ReturnT.SUCCESS;
        }

        //更新数据库文章分值
        //调用文章的更新方法
        //方法引用
        aggBehaviorList.forEach(hotArticleService::updateApArticle);

        log.info("热文章分值更新 调度任务完成....");
        return ReturnT.SUCCESS;
    }


    /**
     * 按照文章分组  进行聚合统计  得到待更新的数据列表
     * @param behaviorList
     * @return
     */
    private List<AggBehaviorDTO> getAggBehaviorList(List<NewBehaviorDTO> behaviorList) {
        //创建文章的聚合对象  接收处理后的结果
        List<AggBehaviorDTO> aggBehaviorDTOList = new ArrayList<>();

        //按照文章的id分组  获取对应分组下的文章列表
        //NewBehaviorDTO 文章对应的一些行为数据
        //按照文章的id分组  map中前面的是文章id  后面的list集合是文章行为的集合
        Map<Long, List<NewBehaviorDTO>> map = behaviorList.stream()
                .collect(Collectors.groupingBy(NewBehaviorDTO::getArticleId));

        //计算每个分组的结果
        //遍历map    文章id      对应的文章的行为集合
        map.forEach((articleId ,messList) ->{
            //返回的是每个文章id和所有的行为对象所在的对象
            Optional<AggBehaviorDTO> reduceResult =

                    //行为集合stream流  单个行为对象
                    //行为集合中的每一个行为
                    messList.stream().map(behavior ->{
                        //创建一个文章的行为对象(文章id 和 四种行为的数量)
                        //行为对象中之后一种行为数量
                        //将每个行为数据  都封装为聚合行为类型
                        //每个文章id和齐对应的所有的行为次数
                        AggBehaviorDTO aggBehavior = new AggBehaviorDTO();
                        //设置分组后的文章的id
                        aggBehavior.setArticleId(articleId);
                        //条件是行为对象的类型
                        switch (behavior.getType()){
                            //如果这个行为是是点赞的话
                            //就讲agg的点赞属性修改成行为对象的次数
                            case LIKES:
                                aggBehavior.setLike(behavior.getAdd());
                                break;
                            case VIEWS:
                                aggBehavior.setView(behavior.getAdd());
                                break;
                            case COMMENT:
                                aggBehavior.setComment(behavior.getAdd());
                                break;
                            case COLLECTION:
                                aggBehavior.setCollect(behavior.getAdd());
                                break;
                            default:
                        }
                        //返回的就是文章id相同的一种行为对象
                        return aggBehavior;

                        //合并方法  将流中的数据 两两合并
                        //代表前面流中返回的相同id的行为对象
                        //需要两两相加 将文章id相同的行为对象合并成一个行为对象
                        //两个参数代表流中两两相加的两个返回的行为对象
                    }).reduce((a1 , a2) ->{
                        a1.setLike(a1.getLike() + a2.getLike());
                        a1.setComment(a1.getComment() + a2.getComment());
                        a1.setView(a1.getView() + a2.getView());
                        a1.setCollect(a1.getCollect() + a2.getCollect());
                        return a1;
            });
            //判断是否存在
            if (reduceResult.isPresent()){
                //如果存在
                //集合结果
                //每一个id 的 行为聚合对象
                AggBehaviorDTO aggBehaviorDTO = reduceResult.get();
                log.info("热点文章 聚合计算结果  ===>{}", aggBehaviorDTO);
                aggBehaviorDTOList.add(aggBehaviorDTO);
            }
        });

        return aggBehaviorDTOList;
    }


    /**
     * 获取redis中的待处理的数据
     * @return
     */
    private List<NewBehaviorDTO> getRedisBehaviorList() {
        //获取redis行为列表中的待处理数据
        ListOperations<String, String> listOperations = redisTemplate.opsForList();

        //得到当前行为数据的数量
        Long size = listOperations.size(HotArticleConstants.HOT_ARTICLE_SCORE_BEHAVIOR_LIST);

        //采用管道命令 , 让多个命令保持原子性
        //获取十秒钟的热点文章数据
        List result = redisTemplate.executePipelined(new RedisCallback<List<NewBehaviorDTO>>() {
            @Override
            public List<NewBehaviorDTO> doInRedis(RedisConnection connection) {
                //开启管道命令
                connection.openPipeline();

                //获取的数据之后需要删除获取的数据
                //获取 0 - size-1 的所有参数的集合
                // 0 是第一个数据的下标  行为数据的长度-1  就是获取所有的数据
                connection.lRange(HotArticleConstants
                        .HOT_ARTICLE_SCORE_BEHAVIOR_LIST.getBytes(StandardCharsets.UTF_8), 0, (size - 1));

                //阶段size 到 -1 的后续的集合数据
                //删除掉刚刚获取的数据
                connection.lTrim(HotArticleConstants
                        .HOT_ARTICLE_SCORE_BEHAVIOR_LIST.getBytes(StandardCharsets.UTF_8), size, -1);

                return null;
            }
        }, RedisSerializer.string());

        //判断获取的redis中的文章行为集合
        if (result.size() > 0){
           List<String> listData =  (List<String>)result.get(0);
           return listData.stream().map(str -> JSON.parseObject(str,NewBehaviorDTO.class))
                   .collect(Collectors.toList());
        }
        return null;
    }
}